#pragma once
#include "../acl_cpp_define.hpp"
#include "../stdlib/string.hpp"
#include "../stdlib/noncopyable.hpp"
#include <map>

namespace acl
{

/**
 * SQL 查询语句查询器，该类会自动对 sql 中的一些特殊字符进行转义，使用方式类似于
 * java hibernate 的 SQL 语句构建方式
 */
class ACL_CPP_API query : public noncopyable
{
public:
	/**
	 * 构造函数
	 */
	query();

	/**
	 * 清理sql_buf_,清理参数集合
	 */
	~query();

	// ----------------------------------------------------------------------------

	/**
	 * 用户可以设置的参数类型
	 */
	typedef enum
	{
		DB_PARAM_CHAR,
		DB_PARAM_SHORT,
		DB_PARAM_INT32,
		DB_PARAM_INT64,
		DB_PARAM_FLOAT,
		DB_PARAM_DOUBLE,
		DB_PARAM_STR
	} db_param_type;

	/**
	 * 保存用户的查询参数
	 */
	class query_param
	{
	public:
		char type;              // 查询参数类型，在db_param_type枚举类型中
		int  dlen;              // 查询参数所占字节数
		int  precision;         // 参数是浮点数时需要设置精度
		union
		{
			char  c;
			short s;
			int   n;
			long long int l;
			double d;
			float f;
			char  S[1];
		} v;                    // 保存实际的参数
	};

	// ----------------------------------------------------------------------------

	/**
	 * 创建 sql 语句，变参方式，用法和 printf 类似，保存给sql_
	 * @param sql_fmt {const char*} sql 语句，格式如：
	 *  select * from xxx where name = :name and len >= %d
	 *  其中的 :name, 将由 set_parameter 中的值进行替换, len 为整形值
	 * @return {query&}
	 */
	query& create_sql(const char* sql_fmt, ...) ACL_CPP_PRINTF(2, 3);

	/**
	 * 创建 sql 语句，非变参方式，保存给sql_
	 * @param sql {const char*}  sql 语句，格式如：
	 *  select * from xxx where name = :name and len >= :len
	 *  其中的 :name, :len 将由 set_parameter 中的值进行替换
	 * @return {query&}
	 */
	query& create(const char* sql);

	// ----------------------------------------------------------------------------

	/**
	 * 设置字符串类型的变量值
	 * 参数类型：DB_PARAM_STR
	 *
	 * @param name {const char*} 变量名
	 * @param value {const char*} 变量值
	 * @return {query&}
	 */
	query& set_parameter(const char* name, const char *value);

	/**
	 * 以变参方式设置字符串类型变量值，调用set_vformat实现
	 * 参数类型：DB_PARAM_STR
	 *
	 * @param name {const char*} 变量名
	 * @param fmt {const char*} 变参值格式
	 * @return {query&}
	 */
	query& set_format(const char* name, const char* fmt, ...)
		ACL_CPP_PRINTF(3, 4);

	/**
	 * 以变参方式设置字符串类型变量值
	 * 参数类型：DB_PARAM_STR
	 *
	 * @param name {const char*} 变量名
	 * @param fmt {const char*} 变参值格式
	 * @param ap {va_list} 变参值列表
	 * @return {query&}
	 */
	query& set_vformat(const char* name, const char* fmt, va_list ap);

	/**
	 * 设置字符类型的变量值
	 * 参数类型：DB_PARAM_CHAR
	 *
	 * @param name {const char*} 变量名
	 * @param value {char} 变量值
	 * @return {query&}
	 */
	query& set_parameter(const char* name, char value);

	/**
	 * 设置 16 位短整类型的变量值
	 * 参数类型：DB_PARAM_SHORT
	 *
	 * @param name {const char*} 变量名
	 * @param value {short} 变量值
	 * @return {query&}
	 */
	query& set_parameter(const char* name, short value);

	/**
	 * 设置 32 位短整类型的变量值
	 * 参数类型：DB_PARAM_INT32
	 *
	 * @param name {const char*} 变量名
	 * @param value {int} 变量值
	 * @return {query&}
	 */
	query& set_parameter(const char* name, int value);

	/**
	 * 设置单精度浮点类型的变量值
	 * 参数类型：DB_PARAM_FLOAT
	 *
	 * @param name {const char*} 变量名
	 * @param value {float} 单精度浮点类型
	 * @param precision {int} 尾数的精度值
	 * @return {query&}
	 */
	query& set_parameter(const char* name, float value, int precision = 8);

	/**
	 * 设置双精度浮点类型的变量值
	 * 参数类型：DB_PARAM_DOUBLE
	 *
	 * @param name {const char*} 变量名
	 * @param value {double} 双精度浮点类型
	 * @param precision {int} 尾数的精度值
	 * @return {query&}
	 */
	query& set_parameter(const char* name, double value, int precision = 8);

	/**
	 * 设置 64 位短整类型的变量值
	 * 参数类型：DB_PARAM_INT64
	 *
	 * @param name {const char*} 变量名
	 * @param value {long long int} 变量值
	 * @return {query&}
	 */
#if defined(_WIN32) || defined(_WIN64)
	query& set_parameter(const char* name, __int64 value);
#else
	query& set_parameter(const char* name, long long int value);
#endif

	/**
	 * 设置日期(time_t)类型的变量值
	 * 参数类型：DB_PARAM_STR
	 *
	 * @param name {const char*} 变量名
	 * @param value {time_t} 变量值
	 * @param fmt {const char*} 日期格式
	 * @return {query&}
	 */
	query& set_date(const char* name, time_t value, const char* fmt = "%Y-%m-%d %H:%M:%S");

	/**
	 * 清空查询器上一次的缓存参数params_，当该 SQL 查询器对象被多次使用时，应该提前调用本函数清除之前的 SQL 查询器状态
	 */
	void reset();

	// ----------------------------------------------------------------------------

	/**
	 * 批量设置参数直接给入参数集合params拷贝给本类params_
	 */
	query& set_params(const std::map<string, query_param*>& params);

	/**
	 * 获取本类params_
	 */
	std::map<acl::string, acl::query::query_param*>& get_params();

	// ----------------------------------------------------------------------------

	/**
	 * 对查询 sql 语句进行转义后返回给调用者
	 * @return {const string&}
	 */
	const string& to_string();

	/**
	 * 对 sql 中的一些特殊字符进行转义处理，以防止 SQL 注入问题
	 * @param in {const char*} 变量值
	 * @param len {size_t} in 数据长度
	 * @param out {string&} 存储转换后的结果的缓冲区，该参数输入后会先被清空
	 * @return {const string&} 转义处理后的结果(其实是 out 的地址引用)
	 */
	static const string& escape(const char* in, size_t len, string& out);

	// ----------------------------------------------------------------------------

	/**
	 * 按照用户要求获取指定格式的日志字符串
	 *
	 * 将时间转换成 DateTime 格式的字符串(YYYY-MM-DD HH:MM:SS)
	 * @param t {time_t} 时间截
	 * @param out {string&} 存储转换结果的缓冲区
	 * @param fmt {const char*} 日期格式，在 _WIN32 下必须保证该格式的正确性，
	 *  否则 _WIN32 API 会产生断言，格式如："%Y-%m-%d %H:%M:%S"
	 * @return {const char*} 转换后缓冲区地址，若返回 NULL 则表示转换失败
	 */
	static const char* to_date(time_t t, string& out, const char* fmt = "%Y-%m-%d %H:%M:%S");


private:
	std::map<acl::string, acl::query::query_param*> params_;    // 以<key, value>的形式组织起用户设置的参数
	string sql_;                                                // 用户给入带参数占位符的sql
	string* sql_buf_;                                           // 将sql_中的参数占位符使用params_填充后构造出的可实际查询的sql
	string buf_;

	/**
	 * 从params_中删除指定key
	 */
	void del_param(const string& key);

	/**
	 * 遍历sql_的过程中，分割占位符得到key名称，按照key名称从params_中取出值填充
	 */
	bool append_key(string& buf, char* key);
};

} // namespace acl
